package org.io4j.resource;

import java.io.InputStream;
import java.net.URL;

import org.io4j.InputOutputException;
import org.io4j.util.Argument;

/**
 * A {@code Resource} that can be found on a classpath. Uses either
 * {@code ClassLoader} or a {@code Class} to locate the resource.
 */
public class ClassPathResource extends BaseResource {

	private final String path;
	private final ClassLoader classLoader;
	private final Class<?> clazz;

	/**
	 * Constructs a resource from a classpath location. Uses thread context
	 * {@code ClassLoader} to load the resource.
	 * 
	 * @param path
	 *           Path relative to classpath root
	 * @throws IllegalArgumentException
	 *            if path is {@code null} or empty
	 * @see java.lang.ClassLoader#getResource(String)
	 */
	public ClassPathResource(String path) {
		this(path, (ClassLoader) null);
	}

	/**
	 * Constructs a resource from a classpath location.
	 * 
	 * @param path
	 *           Path relative to classpath root
	 * @param classLoader
	 *           {@code ClassLoader} to use to locate a resource
	 * @see java.lang.ClassLoader#getResource(String)
	 */
	public ClassPathResource(String path, ClassLoader classLoader) {
		Argument.isNotEmpty(path, "Path is null or empty");
		this.path = normalisePath(path);

		if (classLoader != null) {
			this.classLoader = classLoader;
		} else {
			this.classLoader = getDefaultClassLoader();
		}
		this.clazz = null;
	}

	private static String normalisePath(String path) {
		String normalisedPath = path.replace('\\', '/');
		if (normalisedPath.startsWith("/")) {
			return normalisedPath.substring(1);
		}
		return normalisedPath;
	}

	private static ClassLoader getDefaultClassLoader() {
		ClassLoader classLoader = null;
		try {
			classLoader = Thread.currentThread().getContextClassLoader();
		} catch (SecurityException ignored) {
		}
		if (classLoader == null) {
			classLoader = ClassPathResource.class.getClassLoader();
		}
		return classLoader;
	}

	/**
	 * Constructs a resource from a classpath location.
	 * 
	 * @param path
	 *           Path relative to a specified class
	 * @param clazz
	 *           {@code Class} to use to locate a resource
	 * @see java.lang.ClassLoader#getResource(String)
	 */
	public ClassPathResource(String path, Class<?> clazz) {
		Argument.isNotEmpty(path, "Path is null or empty");
		Argument.isNotNull(clazz, "Class is null");
		this.path = path;
		this.classLoader = null;
		this.clazz = clazz;
	}

	public InputStream openInputStream() {
		InputStream inputStream;
		if (clazz != null) {
			inputStream = clazz.getResourceAsStream(path);
		} else {
			inputStream = classLoader.getResourceAsStream(path);
		}

		if (inputStream == null) {
			throw new InputOutputException("Could not open '" + path + "' for reading",
					createResourceResolutionException());
		}
		return inputStream;
	}

	public URL getUrl() {
		URL url;
		if (clazz != null) {
			url = clazz.getResource(path);
		} else {
			url = classLoader.getResource(path);
		}
		if (url == null) {
			throw createResourceResolutionException();
		}
		return url;
	}

	private ResourceResolutionException createResourceResolutionException() {
		String resourceLoader;
		if (clazz != null) {
			resourceLoader = "class " + clazz.getName();
		} else {
			resourceLoader = "classloader " + classLoader;
		}
		return new ResourceResolutionException(String.format(
				"Classpath resource '%s' could not be located using %s", path, resourceLoader));
	}

	public String getName() {
		return getUrl().toString();
	}
}
